
#include "bb_protocol.h"
#include "bb_buffer.h"
#include "gkt_os.h"
#include "gkt_debug.h"
#include <string.h>

#ifdef GKT_CONFIG_BACKBOARD_THREAD_PRIORITY
#define BB_TRANSPORT_THREAD_PRIORITY	GKT_CONFIG_BACKBOARD_THREAD_PRIORITY
#else
#define BB_TRANSPORT_THREAD_PRIORITY	NORMAL
#endif

#ifdef GKT_CONFIG_BACKBOARD_THREAD_STACKSIZE
#define BB_TRANSPORT_THREAD_STACKSIZE	GKT_CONFIG_BACKBOARD_THREAD_STACKSIZE
#else
#define BB_TRANSPORT_THREAD_STACKSIZE	0x1000
#endif


#define BB_TRANSPORT_MUTEX_TIMEOUT	100
#define BB_TRANSPORT_ACK_TIMEOUT	200
#define BB_TRANSPORT_HEARTBEAT_TIMEOUT	2000

enum {
	BB_TRANSPORT_CTL_ACK = 0,
	BB_TRANSPORT_CTL_APP_PACKET,
	BB_TRANSPORT_CTL_CFG_PACKET,
	BB_TRANSPORT_CTL_MAX
};

#define BB_TRANSPORT_APP_TRANS_ID_MAX	0xF0
#define BB_TRANSPORT_CFG_TRANS_ID	0xFF

enum {
	BB_TRANSPORT_CFG_ID_HEARTBEAT = 0,
	BB_TRANSPORT_CFG_ID_MAX
};

#define BB_TRANSPORT_LOOPS_MAX	3

enum {
	BB_TRANSPORT_TX_ST_IDLE = 0,
	BB_TRANSPORT_TX_ST_DATA,	/* sending data */
	BB_TRANSPORT_TX_ST_ACK,		/* sending ack */
	BB_TRANSPORT_TX_ST_HB,		/* sending heartbeat */
	BB_TRANSPORT_TX_ST_MAX
};

#define BB_TRANSPORT_TX_PENDING_DATA	(1U << 0)
#define BB_TRANSPORT_TX_PENDING_ACK		(1U << 1)

typedef struct _bb_transport {
	gkt_mutex_t	mutex;

	uint16_t	rx_trans_id;
	uint16_t	rx_data_length;
	uint8_t	*rx_data;
	uint8_t	*rx_data_alloc;
	uint8_t	rx_seg_nums;
	uint8_t	rx_seg_counts;
	uint8_t	rx_seg_bits;
	uint8_t	rx_packet_handled;

	gkt_semaphore_t	tx_pending_sem;
	gkt_semaphore_t	tx_ack_sem;
	uint8_t	tx_status;
	uint8_t	tx_data_inprocess;
	uint8_t	tx_pending;
	uint8_t	tx_trans_id;
	uint8_t	tx_seg_unack_bits;	/* seg unack bits */
	uint8_t tx_ack_packet[BB_TRANSPORT_PACKET_SIZE(1)];
	uint8_t	tx_heartbeat_packet[BB_TRANSPORT_PACKET_SIZE(1)];
	uint8_t	tx_packet[BB_TRANSPORT_PACKET_SIZE(BB_TRANSPORT_PAYLOAD_MAXSIZE)];
} bb_transport_s;
static bb_transport_s s_bb_transport;

#define bb_transport_mutex_lock()	\
	gkt_mutex_lock(s_bb_transport.mutex, BB_TRANSPORT_MUTEX_TIMEOUT)
#define bb_transport_mutex_unlock()	\
	gkt_mutex_unlock(s_bb_transport.mutex)

static void bb_transport_send_idle(void);

static void bb_transport_send_heartbeat_inner(void)
{
	uint8_t *trans_heartbeat;

	trans_heartbeat = &s_bb_transport.tx_heartbeat_packet[BB_NETWORK_PAYLOAD_OFFSET];
	trans_heartbeat[BB_TRANSPORT_CTL_SI_OFFSET] = BB_TRANSPORT_CTL_CFG_PACKET << BB_TRANSPORT_CTL_SHIFT;
	trans_heartbeat[BB_TRANSPORT_TRANSID_OFFSET] = BB_TRANSPORT_CFG_TRANS_ID;
	trans_heartbeat[BB_TRANSPORT_PAYLOAD_OFFSET] = BB_TRANSPORT_CFG_ID_HEARTBEAT;
	bb_network_send_packet(trans_heartbeat, BB_TRANSPORT_PAYLOAD_OFFSET + 1);
}


void bb_transport_send_heartbeat(void)
{
	int retval;

	retval = bb_transport_mutex_lock();
	if (retval != GKT_SUCCESS) {
		gkt_error("bb_transport: <TX-HB> (0) timeout!\n");
		return;
	}

	gkt_trace("bb_transport: <TX-HB> status(%u), pending(0x%x)\n",
		s_bb_transport.tx_status, s_bb_transport.tx_pending);

	if (BB_TRANSPORT_TX_ST_IDLE == s_bb_transport.tx_status) {
		s_bb_transport.tx_status = BB_TRANSPORT_TX_ST_HB;
		s_bb_transport.tx_pending &= ~(1U << BB_TRANSPORT_TX_ST_HB);
		bb_transport_mutex_unlock();

		bb_transport_send_heartbeat_inner();
		bb_transport_send_idle();
	}
	else {
		s_bb_transport.tx_pending |= 1U << BB_TRANSPORT_TX_ST_HB;
		bb_transport_mutex_unlock();
	}
	
}

static void bb_transport_send_ack_inner(void)
{
	uint8_t *trans_ack;

	trans_ack = &s_bb_transport.tx_ack_packet[BB_NETWORK_PAYLOAD_OFFSET];
	trans_ack[BB_TRANSPORT_CTL_SI_OFFSET] = BB_TRANSPORT_CTL_ACK << BB_TRANSPORT_CTL_SHIFT;
	trans_ack[BB_TRANSPORT_TRANSID_OFFSET] = (uint8_t)s_bb_transport.rx_trans_id;
	trans_ack[BB_TRANSPORT_PAYLOAD_OFFSET] = s_bb_transport.rx_seg_bits;
	bb_network_send_packet(trans_ack, BB_TRANSPORT_PAYLOAD_OFFSET + 1);
}
static void bb_transport_send_ack(void)
{
	int loops, retval;

	loops = 0;
check_status:
	retval = bb_transport_mutex_lock();
	if (retval != GKT_SUCCESS) {
		gkt_error("bb_transport: <TX-ACK> mutex timeout!\n");
		return;
	}

	gkt_trace("bb_transport: <TX-ACK> (%u) status(%u), pending(0x%x)\n",
		loops, s_bb_transport.tx_status, s_bb_transport.tx_pending);

	if (BB_TRANSPORT_TX_ST_IDLE == s_bb_transport.tx_status) {
		s_bb_transport.tx_status = BB_TRANSPORT_TX_ST_ACK;
		s_bb_transport.tx_pending &= ~(1U << BB_TRANSPORT_TX_ST_ACK);
		gkt_mutex_unlock(s_bb_transport.mutex);

		bb_transport_send_ack_inner();
		bb_transport_send_idle();
	}
	else if (loops < BB_TRANSPORT_LOOPS_MAX) {
		s_bb_transport.tx_pending |= 1U << BB_TRANSPORT_TX_ST_ACK;
		bb_transport_mutex_unlock();

		loops++;
		gkt_semaphore_acquire(s_bb_transport.tx_pending_sem, 
				BB_TRANSPORT_ACK_TIMEOUT);
		goto check_status;
	}
	else {
		s_bb_transport.tx_pending &= ~(1U << BB_TRANSPORT_TX_ST_ACK);
		bb_transport_mutex_unlock();
	}
}

static void bb_transport_send_data_inner(const uint8_t *data, 
				uint32_t length, uint8_t seg_nums, 
				uint8_t seg_unack_bits)
{
	uint8_t *trans_packet;
	uint32_t seg_offset, seg_length, i;
	uint8_t seg_index;

	gkt_trace("bb_transport_send_data_inner: seg_nums(%u), seg_unack_bits(%u)\n", 
		seg_nums, seg_unack_bits);

	/* clear ack semaphore */
	gkt_semaphore_acquire(s_bb_transport.tx_ack_sem, 0);

	trans_packet = &s_bb_transport.tx_packet[BB_TRANSPORT_PACKET_OFFSET];
	for (seg_index = 0; seg_index < seg_nums; seg_index++) {
		if (seg_unack_bits & (1U << seg_index)) {

			seg_offset = BB_TRANSPORT_PAYLOAD_MAXSIZE * seg_index;
			if (seg_index < (seg_nums - 1))
				seg_length = BB_TRANSPORT_PAYLOAD_MAXSIZE;
			else
				seg_length = length - seg_offset;

			trans_packet[BB_TRANSPORT_CTL_SI_OFFSET] = 
					(BB_TRANSPORT_CTL_APP_PACKET << BB_TRANSPORT_CTL_SHIFT)
					| ((seg_nums - 1) << BB_TRANSPORT_LSI_BITS)
					| (seg_index << BB_TRANSPORT_CSI_BITS);
			trans_packet[BB_TRANSPORT_TRANSID_OFFSET] = s_bb_transport.tx_trans_id;
			for (i = 0; i < seg_length; i++)
				trans_packet[BB_TRANSPORT_PAYLOAD_OFFSET + i] = data[seg_offset + i];

			seg_length += BB_TRANSPORT_PAYLOAD_OFFSET;
			bb_network_send_packet(trans_packet, seg_length);
		}
	}
}

int bb_transport_send_data(const uint8_t *data, uint32_t length)
{
	uint8_t seg_nums, seg_unack_bits;
	int loops, sem_loops, retval;

	loops = 0;
	sem_loops = 0;
	seg_nums = (length + BB_TRANSPORT_PAYLOAD_MAXSIZE - 1) / BB_TRANSPORT_PAYLOAD_MAXSIZE;
	seg_unack_bits = (1U << seg_nums) - 1;
	gkt_trace("bb_transport: <TX-DAT> length(%u), seg_nums(%u)\n", 
			length, seg_nums);

check_status:
	retval = bb_transport_mutex_lock();
	if (retval != GKT_SUCCESS) {
		s_bb_transport.tx_data_inprocess = 0;
		gkt_error("bb_transport: <TX-DAT> (%u) mutex timeout!\n", loops);
		return retval;
	}

	gkt_trace("bb_transport: <TX-DAT> (%u-%u) status(%u-%u), pending(0x%x)\n",
		loops, sem_loops, s_bb_transport.tx_status, 
		s_bb_transport.tx_data_inprocess, s_bb_transport.tx_pending);

	if (BB_TRANSPORT_TX_ST_IDLE == s_bb_transport.tx_status) {
		s_bb_transport.tx_status = BB_TRANSPORT_TX_ST_DATA;
		s_bb_transport.tx_data_inprocess = 1;
		s_bb_transport.tx_pending &= ~(1U << BB_TRANSPORT_TX_ST_DATA);
		if (!loops) {
			s_bb_transport.tx_trans_id++;
			s_bb_transport.tx_seg_unack_bits = seg_unack_bits;
		}
		bb_transport_mutex_unlock();

		sem_loops = 0;
	}
	else if (sem_loops < BB_TRANSPORT_LOOPS_MAX) {
		s_bb_transport.tx_pending |= 1U << BB_TRANSPORT_TX_ST_DATA;
		bb_transport_mutex_unlock();

		sem_loops++;
		gkt_semaphore_acquire(s_bb_transport.tx_pending_sem, 
				BB_TRANSPORT_ACK_TIMEOUT);
		goto check_status;
	}
	else {
		s_bb_transport.tx_data_inprocess = 0;
		s_bb_transport.tx_pending &= ~(1U << BB_TRANSPORT_TX_ST_DATA);
		bb_transport_mutex_unlock();

		return GKT_ETIMEOUT;
	}

	bb_transport_send_data_inner(data, length, seg_nums, seg_unack_bits);
	bb_transport_send_idle();

	retval = gkt_semaphore_acquire(s_bb_transport.tx_ack_sem, 
					BB_TRANSPORT_ACK_TIMEOUT);
	if (retval != GKT_SUCCESS)
		gkt_error("bb_transport: <TX-DAT> wait ack_sem failed %d\n", retval);

	retval = bb_transport_mutex_lock();
	if (GKT_SUCCESS == retval) {
		seg_unack_bits = s_bb_transport.tx_seg_unack_bits;
		if (!seg_unack_bits) {
			s_bb_transport.tx_data_inprocess = 0;
			bb_transport_mutex_unlock();

			gkt_trace("bb_transport: <TX-DAT> *** SUCCESS ***\n");
			return GKT_SUCCESS;
		}
		else
			bb_transport_mutex_unlock();
	}

	loops++;
	if (loops < BB_TRANSPORT_LOOPS_MAX)
		goto check_status;

	s_bb_transport.tx_data_inprocess = 0;

	return GKT_ETIMEOUT;
}

static void bb_transport_send_idle(void)
{
	int retval;

	s_bb_transport.tx_status = BB_TRANSPORT_TX_ST_IDLE;

	retval = bb_transport_mutex_lock();
	if (retval != GKT_SUCCESS) {
		gkt_error("bb_transport: <TX-IDLE> mutex timeout!\n");
		return;
	}

	if (s_bb_transport.tx_pending) {
		if (s_bb_transport.tx_pending & (1U << BB_TRANSPORT_TX_ST_HB)) {
			s_bb_transport.tx_status = BB_TRANSPORT_TX_ST_HB;
			s_bb_transport.tx_pending &= ~(1U << BB_TRANSPORT_TX_ST_HB);
			bb_transport_mutex_unlock();

			bb_transport_send_heartbeat_inner();

			bb_transport_mutex_lock();
			s_bb_transport.tx_status = BB_TRANSPORT_TX_ST_IDLE;
		}

		if ((s_bb_transport.tx_pending & (1U << BB_TRANSPORT_TX_ST_ACK))
			|| (s_bb_transport.tx_pending & (1U << BB_TRANSPORT_TX_ST_DATA)))
		{
			gkt_semaphore_release(s_bb_transport.tx_pending_sem);
		}
	}

	bb_transport_mutex_unlock();
}

static void bb_transport_data_received(uint8_t seg_value, 
				const uint8_t *packet, uint32_t length)
{
	uint8_t seg_nums, seg_index, trans_id;
	uint32_t buff_maxsize, offset;

	seg_nums = BB_TRANSPORT_ITEM(LSI, seg_value) + 1;
	seg_index = BB_TRANSPORT_ITEM(CSI, seg_value);

	if ((seg_nums > BB_TRANSPORT_SEG_MAXNUMS) || (seg_index >= seg_nums)) {
		gkt_error("bb_transport: segment info failed!\n");
		return;
	}

	trans_id = packet[BB_TRANSPORT_TRANSID_OFFSET];

	gkt_trace("bb_transport_data_received: trans_id(%u), seg_nums(%u), seg_index(%u)\n",
		trans_id, seg_nums, seg_index);

	if ((uint16_t)trans_id != s_bb_transport.rx_trans_id) {
		gkt_trace("bb_transport: <RX> [NEW] trans_id(%u) seg(%u/%u), len(%u)\n",
			trans_id, seg_index, seg_nums, length);

		if (1 == seg_nums) {
			s_bb_transport.rx_data = (uint8_t*)&packet[BB_TRANSPORT_PAYLOAD_OFFSET];
			s_bb_transport.rx_data_length = length - BB_TRANSPORT_PAYLOAD_OFFSET;
			s_bb_transport.rx_seg_counts = 1;
			s_bb_transport.rx_seg_bits = 1;
		}
		else {
			if (s_bb_transport.rx_data_alloc
				&& (seg_nums > s_bb_transport.rx_seg_nums))
			{
				bb_buffer_free(s_bb_transport.rx_data_alloc);
				s_bb_transport.rx_data_alloc = NULL;
			}
			if (!s_bb_transport.rx_data_alloc) {
				buff_maxsize = BB_TRANSPORT_PAYLOAD_MAXSIZE * seg_nums;
				s_bb_transport.rx_data_alloc = bb_buffer_alloc(buff_maxsize);
				if (!s_bb_transport.rx_data_alloc) {
					gkt_error("bb_transport: <RX> alloc rx_data failed!\n");
					return;
				}
			}

			s_bb_transport.rx_data = s_bb_transport.rx_data_alloc;
			s_bb_transport.rx_data_length = 0;
			s_bb_transport.rx_seg_counts = 0;
			s_bb_transport.rx_seg_bits = 0;
		}

		s_bb_transport.rx_trans_id = (uint16_t)trans_id;
		s_bb_transport.rx_seg_nums = seg_nums;
		s_bb_transport.rx_packet_handled = 0;
	}

	if (seg_nums > 1) {
		if (seg_nums != s_bb_transport.rx_seg_nums) {
			gkt_error("bb_transport: (%u) seg_nums(%u-%u) unmatch!\n", 
				trans_id, seg_nums, s_bb_transport.rx_seg_nums);
			return;
		}
		else if ((seg_index < (seg_nums - 1))
			&& (length != BB_NETWORK_PAYLOAD_MAXSIZE))
		{
			gkt_error("bb_transport: (%u) seg(%u) length(%u) invalid!\n", 
				trans_id, seg_index, length);
			return;
		}

		if (!(s_bb_transport.rx_seg_bits & (1U << seg_index))) {
			offset = BB_TRANSPORT_PAYLOAD_MAXSIZE * seg_index;
			memcpy(&s_bb_transport.rx_data[offset], 
				&packet[BB_TRANSPORT_PAYLOAD_OFFSET], 
				length - BB_TRANSPORT_PAYLOAD_OFFSET);

			s_bb_transport.rx_seg_bits |= 1U << seg_index;
			s_bb_transport.rx_seg_counts++;
			s_bb_transport.rx_data_length += length - BB_TRANSPORT_PAYLOAD_OFFSET;
		}

		while (++seg_index < seg_nums) {
			if (!(s_bb_transport.rx_seg_bits & (1U << seg_index)))
				return;
		}
	}

	bb_transport_send_ack();

	gkt_trace("bb_transport_data_received: rx_seg_counts(%u), rx_seg_nums(%u), rx_packet_handled(%u)\n",
		s_bb_transport.rx_seg_counts, s_bb_transport.rx_seg_nums, s_bb_transport.rx_packet_handled);

	if ((s_bb_transport.rx_seg_counts == s_bb_transport.rx_seg_nums) 
		&& !s_bb_transport.rx_packet_handled) 
	{
		s_bb_transport.rx_packet_handled = 1;
		bb_application_packet_received(s_bb_transport.rx_data, 
			s_bb_transport.rx_data_length);
	}
}

static void bb_transport_ack_received(const uint8_t *packet, uint32_t length)
{
	uint8_t trans_id, seg_acked_bits;
	int retval;

	trans_id = packet[BB_TRANSPORT_TRANSID_OFFSET];
	seg_acked_bits = packet[BB_TRANSPORT_PAYLOAD_OFFSET];
	gkt_trace("bb_transport: ack_received - tx_status(%u-%u), trans_id(%u-%u), bits(0x%x)\n",
		s_bb_transport.tx_status, s_bb_transport.tx_data_inprocess, 
		trans_id, s_bb_transport.tx_trans_id, seg_acked_bits);
	
	retval = bb_transport_mutex_lock();
	if (GKT_SUCCESS == retval) {
		if (s_bb_transport.tx_data_inprocess 
			&& (trans_id == s_bb_transport.tx_trans_id))
		{
			s_bb_transport.tx_seg_unack_bits &= ~seg_acked_bits;
			gkt_semaphore_release(s_bb_transport.tx_ack_sem);
		}
		bb_transport_mutex_unlock();
	}
	else
		gkt_error("bb_transport: ack_received - mutex timeout!\n");
}

void bb_transport_packet_received(const uint8_t *packet, 
				uint32_t length)
{
	uint8_t value, ctl;

	value = packet[BB_TRANSPORT_CTL_SI_OFFSET];

	ctl = BB_TRANSPORT_ITEM(CTL, value);

	gkt_trace("bb_transport_packet_received: length(%u), ctl(%u)\n", length, ctl);
	
	if (ctl >= BB_TRANSPORT_CTL_MAX) {
		gkt_error("bb_transport: invalid ctl(%u)\n", ctl);
		return;
	}

	if (BB_TRANSPORT_CTL_APP_PACKET == ctl)
		bb_transport_data_received(value, packet, length);
	else if (BB_TRANSPORT_CTL_ACK == ctl)
		bb_transport_ack_received(packet, length);
}

int bb_transport_init(void)
{
	s_bb_transport.mutex = gkt_mutex_create(0);
	if (!s_bb_transport.mutex) {
		gkt_error("bb_transport: create mutex failed!\n");
		return GKT_ECREATE;
	}

	s_bb_transport.tx_pending_sem = gkt_semaphore_create(1, 0);
	if (!s_bb_transport.tx_pending_sem) {
		gkt_error("bb_transport: create tx_pending_sem failed!\n");
		return GKT_ECREATE;
	}

	s_bb_transport.tx_ack_sem = gkt_semaphore_create(1, 0);
	if (!s_bb_transport.tx_ack_sem) {
		gkt_error("bb_transport: create tx_ack_sem failed!\n");
		return GKT_ECREATE;
	}

	s_bb_transport.rx_data_alloc = NULL;
	s_bb_transport.rx_trans_id = ~0;
	s_bb_transport.tx_status = BB_TRANSPORT_TX_ST_IDLE;
	s_bb_transport.tx_pending = 0;
	s_bb_transport.tx_trans_id = ~0;

	return GKT_SUCCESS;
}

